前言
Electron 是使用 JavaScript,HTML 和 CSS 构建跨平台的桌面应用程序的框架,可构建出兼容 Mac、Windows 和 Linux 三个平台的应用程序。
一个跨端框架的设计,有三个问题需要考虑,分别是 UI 渲染、原生 API 以及客户端构建方式。Electron 是如何解决上述问题的呐?
Electron 的跨端原理并不难理解:它通过集成浏览器内核,使用前端的技术来实现不同平台下的渲染,并结合了 Chromium 、Node.js 和用于调用系统本地功能的 API 三大板块。

- Chromium为- Electron提供强大的- UI渲染能力,由于- Chromium本身跨平台,因此无需考虑代码的兼容性。最重要的是,可以使用前端三板斧进行- Electron开发。
- Chromium并不具备原生- GUI的操作能力,因此- Electron内部集成- Node.js,编写- UI的同时也能够调用操作系统的底层- API,例如 path、fs、crypto 等模块。
- Native API为- Electron提供原生系统的- GUI支持,借此- Electron可以调用原生应用程序接口。
总结起来,Chromium 负责页面 UI 渲染,Node.js 负责业务逻辑,Native API 则提供原生能力和跨平台。
上面粗略讲解了 Electron 的跨端原理,下面我们来深究一下。
Chromium 架构
JavaScript 是单线程语言,但浏览器是多线程的,Chromium 作为 Chrome 的实验版,自然也是基于多线程工作机制。(图源: Chromium 官网)

Chromium 的多进程模式主要由三部分组成: 浏览器端(Browser)、渲染器端(Render)、浏览器与渲染器的通信方式(IPC)
1.浏览器进程
浏览器进程 Browser 只有一个,当 Chrome 打开时,进程启动。浏览器为每个渲染进程维护对应的 RenderProcessHost,负责浏览器与渲染器的交互。RenderViewHost 则是与 RenderView 对象进行交互,渲染网页的内容。浏览器与渲染器通过 IPC 进行通信。
2.渲染进程管理
每个渲染进程都有一个全局 RenderProcess 对象,可以管理其与父浏览器进程之间的通信,并维护其全局状态。
3.view 管理
每个渲染器可以维护多个 RenderView 对象,当新开标签页或弹出窗口后,渲染进程就会创建一个 RenderView,RenderView 对象与它在浏览器进程中对应的 RenderViewHost 和 Webkit 嵌入层通信,渲染出网页网页内容(这里是我们日常主要关注的地方)。
Electron 架构解析
Electron 架构参考了 Chromium 的多进程架构模式,即将主进程和渲染进程隔离,并且在 Chromium 多进程架构基础上做一定扩展。
将上面复杂的 Chromium 架构简化:

Chromium 运行时由一个 Browser Process,以及一个或者多个 Renderer Process 构成。Renderer Process 负责渲染页面 Web ,Browser Process 负责管理各个 Renderer Process 以及其他功能(菜单栏、收藏夹等)
下面我们看一下啊 Electron 架构有那些变化?

Electron 架构中仍然使用了 Chromium 的 Renderer Process 渲染界面,Renderer Process 可以有多个,互相独立不干扰。由于 Electron 为其集成了 Node 运行时,Renderer Process 可以调用 Node API。主要负责: 利用 HTML 和 CSS 渲染页面;利用 JavaScript 实现页面交互效果。

相较于 Chromium 架构,Electron 对 Browser 线程做了很多改动,将其更改名 Main Process,每个应用程序只能有一个主线程,主线程位于 Node.js 下运行,因此其可以调用系统底层功能。其主要负责:渲染进程的创建;系统底层功能及原生资源的调用;应用生命周期的控制(包裹启动、推出以及一些事件监听)

经过上面的分析,Electron 多进程的系统架构可以总结为下图:

可以发现,主线程和渲染线程都集成了 Native API 和 Node.js,渲染线程还集成 Chromium 内核,成功实现跨端开发。
Node 与 Chromium
Node 的事件循环与浏览器的事件循环有明显不同,Chromium 既然是 Chrome 的实验版,自然与浏览器实现相同。
Node 的事件循环基于 libuv 实现,而 Chromium 基于 message bump 实现。主线程只能同时运行一个事件循环,因此需要将两个完全不同的事件循环整合起来。
有两种解决方案:
- 使用 libuv实现message bump将Chromium集成到Node.js
- 将 Node.js集成到Chromium
Electron 最初的方案是第一种,使用 libuv 实现 message bump,但不同的 OS 系统 GUI 事件循环差异很大,例如 mac 为 NSRunLoop,Linux 为 glib,实现过程特别复杂,资源消耗和延迟问题也无法得到有效解决,最终放弃了第一种方案。
Electron 第二次尝试使用小间隔的定时器来轮询 GUI 事件循环,但此方案 CPU 占用高,并且 GUI 响应速度慢。
后来 libuv 引入了 backend_fd 概念,backend_fd 轮询事件循环的文件描述符,因此 Electron 通过轮询 backend_fd 来得到 libuv 的新事件实现 Node.js 与 Chromium 事件循环的融合(第二种方案)。
下面这张 PPT 完美的描述了上述过程(图源:Electron: The Event Loop Tightrope - Shelley Vohr | JSHeroes 2019)

参考链接
- Multi-process Architecture
- Essential Electron
- 跨平台技术实战!百度文库跨平台技术快速落地全过程
- Electron Internals: Message Loop Integration
- Electron 开发实战文档资料
后语
我是 战场小包 ,一个快速成长中的小前端,希望可以和大家一起进步。
如果喜欢小包,可以在 掘金 关注我,同样也可以关注我的小小公众号——小包学前端。
一路加油,冲向未来!!!
作者:战场小包 链接:https://juejin.cn/post/7103337772424888356 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。